﻿/*
 * Catalyst - http://catalystobjects.com
 * 
 * New BSD License (BSD)
 * 
 * Copyright (c) 2009, Ashley Brener
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
 * 
 * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
 * 
 * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
 * 
 * Neither the name of Midlet Earth nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Text;
using System.Xml.Serialization;
using SubSonic;
using System.Xml.Schema;
using System.IO;
using System.Xml;
using System.Reflection;
using System.Collections;
using System.Web;

namespace Catalyst
{
	[Serializable]
	public abstract class DataObject<ItemType> : RelationalObject, IRelationLoader
		where ItemType : DataObject<ItemType>, new()
	{
		//public abstract override string GetTableName();

		//public abstract override string GetProviderName();

		internal override void LoadInternal(IDataReader Reader)
		{
			Load(Reader);
		}

		private void loadInternal(TableSchema.TableColumnSettingCollection ColumnSettings)
		{
			Load(ColumnSettings);
		}

		[XmlIgnore]
		public override bool IsLoaded
		{
			get { return (columnSettings.Count > 0); }
		}

		#region IDataObject Members

		private MappingList mappingList;

		private MappingList getMappings()
		{
			if (mappingList == null)
			{
				mappingList = GetMappings();

				if (mappingList == null)
				{
					throw new Exception(String.Format("No mappings found for BusinessObject {0}", GetType().FullName));
				}
			}

			return mappingList;
		}

		public abstract MappingList GetMappings();

		public void LoadRelations(IList<string> RelationNames)
		{
			if (RelationNames != null && RelationNames.Count > 0)
			{
				relationNamesToLoad = new List<string>(RelationNames);

				string relationTableName = null;
				string fkName = null;
				string pkName = null;

				MappingList mappings = getMappings();
				DataRelationMapping mapping = null;

				if (mappings != null)
				{
					foreach (string name in relationNamesToLoad)
					{
						// Case sensitive
						try
						{
							if (mappings[name] != null && (!UseLazyLoading || !checkIfLoaded(name)))
							{
								if (mappings[name] is DataRelationMapping && mappings[name].Value is RelationalObject)
								{
									mapping = (DataRelationMapping)mappings[name];

									TableSchema.Table relationTable = mapping.RelatedTable.Provider.GetTableSchema(mapping.RelatedTable.TableName, mapping.RelatedTable.TableType);

									relationTableName = relationTable.TableName;

									pkName = getPrimaryKeyName();
									fkName = getPrimaryKeyTables()[relationTableName];

									((RelationalObject)mapping.Value).LoadAsRelation(relationTable, fkName, GetDataValue<int>(pkName), mapping.Aggregates);
									
									if (mapping.Value != null)
									{
										if (UseLazyLoading)
										{
											flagObjectAsLoaded(name);
										}

										IRelationLoader loader = mapping.Value as IRelationLoader;

										if (loader != null)
										{
											loader.LoadRelations(relationNamesToLoad);
										}
									}

									break;
								}
							}
						}
						catch (KeyNotFoundException ex) { }
					}
				}
			}
		}

		#endregion

		#region *** Schema ***

		internal TableSchema.TableColumnSettingCollection columnSettings;

		private string getPrimaryKeyName()
		{
			return Schema.PrimaryKey.ColumnName;
		}

		private Dictionary<string, string> pkTables;

		private Dictionary<string, string> getPrimaryKeyTables()
		{
			if (pkTables == null)
			{
				pkTables = new Dictionary<string, string>();

				foreach (TableSchema.PrimaryKeyTable table in Schema.PrimaryKeyTables)
				{
					pkTables.Add(table.Name, table.ColumnName);
				}
			}

			return pkTables;
		}

		private Dictionary<string, TableSchema.ForeignKeyTable> fkTables;

		private Dictionary<string, TableSchema.ForeignKeyTable> getForeignKeyTables()
		{
			if (fkTables == null)
			{
				fkTables = new Dictionary<string, TableSchema.ForeignKeyTable>();

				foreach (TableSchema.ForeignKeyTable table in Schema.ForeignKeys)
				{
					fkTables.Add(table.Name, table);
				}
			}

			return fkTables;
		}

		public DbType GetDBType(string DataColumnName)
		{
			return Schema.GetColumn(DataColumnName).DataType;
		}

		#region GetRelationValue

		///// <summary>
		///// Returns RelationalObject from database
		///// </summary>
		///// <typeparam name="T">Object of type RelationalObject</typeparam>
		///// <param name="Obj"></param>
		///// <param name="RelationName"></param>
		///// <param name="ForeignKeyName"></param>
		///// <param name="PrimaryKeyValue"></param>
		///// <returns></returns>
		//public T GetRelationValue<T>(ref T Obj, string RelationName, string ForeignKeyName, object PrimaryKeyValue)
		//{
		//    return GetRelationValue<T>(ref Obj, RelationName, ForeignKeyName, PrimaryKeyValue, null);
		//}

		///// <summary>
		///// Returns a RelationalObject from database
		///// </summary>
		///// <typeparam name="T">Object of type RelationalObject</typeparam>
		///// <param name="Obj"></param>
		///// <param name="RelationName"></param>
		///// <param name="TableName"></param>
		///// <param name="ForeignKeyName"></param>
		///// <param name="PrimaryKeyValue"></param>
		///// <returns></returns>
		//public T GetRelationValue<T>(ref T Obj, string RelationName, string TableName, string ForeignKeyName, object PrimaryKeyValue)
		//{
		//    return GetRelationValue<T>(ref Obj, RelationName, TableName, ForeignKeyName, PrimaryKeyValue, null);
		//}

		///// <summary>
		///// Returns  a RelationalObject from database
		///// </summary>
		///// <typeparam name="T">Object of type RelationalObject</typeparam>
		///// <param name="Obj"></param>
		///// <param name="RelationName"></param>
		///// <param name="ForeignKeyName"></param>
		///// <param name="PrimaryKeyValue"></param>
		///// <param name="Constraints"></param>
		///// <returns></returns>
		//public T GetRelationValue<T>(ref T Obj, string RelationName, string ForeignKeyName, object PrimaryKeyValue, List<Where> Constraints)
		//{
		//    return GetRelationValue<T>(ref Obj, RelationName, null, ForeignKeyName, PrimaryKeyValue, Constraints);
		//}

		///// <summary>
		///// Returns  a RelationalObject from database
		///// </summary>
		///// <typeparam name="T">Object of type RelationalObject</typeparam>
		///// <param name="Obj"></param>
		///// <param name="RelationName"></param>
		///// <param name="TableName"></param>
		///// <param name="ForeignKeyName"></param>
		///// <param name="PrimaryKeyValue"></param>
		///// <param name="Constraints"></param>
		///// <returns></returns>
		//public T GetRelationValue<T>(ref T Obj, string RelationName, string TableName, string ForeignKeyName, object PrimaryKeyValue, List<Where> Constraints)
		//{
		//    if (validateReferenceObject<T>(Obj, RelationName))
		//    {
		//        RelationalObject relation = Obj as RelationalObject;
		//        relation.LoadAsRelation<T>(TableName, ForeignKeyName, PrimaryKeyValue, Constraints);

		//        flagObjectAsLoaded(RelationName);
		//    }

		//    return Obj;
		//}

		///// <summary>
		///// Returns  a RelationalObject as a collection from cache
		///// </summary>
		///// <typeparam name="T">Object of type BusinessObject</typeparam>
		///// <typeparam name="ListType">Object of type BusinessObjectList</typeparam>
		///// <param name="Obj"></param>
		///// <param name="RelationName"></param>
		///// <param name="ForeignKeyName"></param>
		///// <param name="PrimaryKeyValue"></param>
		///// <returns></returns>
		//public ListType GetRelationValueAsCollection<T, ListType>(ref ListType Obj, string RelationName, string ForeignKeyName, object PrimaryKeyValue)
		//    where T : BusinessObject<T>, new()
		//    where ListType : BusinessObjectList<T, ListType>, new()
		//{
		//    if (validateReferenceObject<ListType>(Obj, RelationName))
		//    {
		//        ListType relationCollection = Obj as ListType;
		//        relationCollection.LoadAsForeign(ForeignKeyName, PrimaryKeyValue);

		//        flagObjectAsLoaded(RelationName);
		//    }
			
		//    return Obj;
		//}

		///// <summary>
		///// Returns a RelationalObject from cache
		///// </summary>
		///// <typeparam name="T">Object of type BusinessObject</typeparam>
		///// <typeparam name="ListType">Object of type BusinessObjectList</typeparam>
		///// <param name="Obj"></param>
		///// <param name="RelationName"></param>
		///// <param name="ForeignKeyName"></param>
		///// <param name="PrimaryKeyValue"></param>
		///// <returns></returns>
		//public T GetRelationValue<T, ListType>(ref T Obj, string RelationName, string ForeignKeyName, object PrimaryKeyValue)
		//    where T : BusinessObject<T>, new()
		//    where ListType : BusinessObjectList<T, ListType>, new()
		//{			
		//    if (validateReferenceObject<T>(Obj, RelationName))
		//    {
		//        T relation = Obj as T;
		//        relation.LoadAsRelation<ListType>(ForeignKeyName, PrimaryKeyValue);

		//        flagObjectAsLoaded(RelationName);
		//    }

		//    return Obj;
		//}

		#endregion

		//private bool validateReferenceObject<T>(T obj, string relationName)
		//{
		//    if (obj == null)
		//    {
		//        Type referenceObjectType = typeof(T);

		//        throw new Exception(String.Format("Referenced Foreign Object of type {0} cannot be null", referenceObjectType.FullName));
		//    }

		//    if (checkIfRequiredToLoad(relationName) && (!UseLazyLoading || !checkIfLoaded(relationName)))
		//    {
		//        return true;
		//    }

		//    return false;
		//}

		private bool checkIfRequiredToLoad(string relationName)
		{
			if (relationNamesToLoad != null)
			{
				if (relationNamesToLoad.Count > 0 && relationNamesToLoad.Contains(relationName))
				{
					return true;
				}
			}
			else
			{
				return true;
			}

			return false;
		}

		//public T GetObjectValue<T>(ref T Obj, string DataColumnName)
		//{
		//    if (schema != null)
		//    {
		//        if (!UseLazyLoading || !checkIfLoaded(DataColumnName))
		//        {
		//            object newObj = GetDataValue<object>(DataColumnName);

		//            if (newObj != null)
		//            {
		//                Tools tools = new Tools();
		//                Obj = tools.ConvertType<T>(newObj);
		//            }

		//            flagObjectAsLoaded(DataColumnName);
		//        }
		//    }

		//    return Obj;
		//}

		private bool hasFullTableSchema = false;

		public object GetObjectValue(string Name)
		{
			if (Schema != null)
			{
				MappingList mappings = getMappings();

				if (mappings.ContainsKey(Name))
				{
					Mapping mapping = mappings[Name];

					if (mapping != null && (!UseLazyLoading || !mapping.IsLoaded))
					{
						if (mapping is DataRelationMapping && mapping.Value is RelationalObject)
						{
							DataRelationMapping dataRelationMapping = (DataRelationMapping)mapping;

							TableSchema.Table relationTable = dataRelationMapping.RelatedTable.Provider.GetTableSchema(dataRelationMapping.RelatedTable.TableName, dataRelationMapping.RelatedTable.TableType);
							
							string relationTableName = relationTable.TableName;

							string pkName = null;
							string fkName = null;

							if (!hasFullTableSchema)
							{
								Schema = Schema.Provider.GetTableSchema(Schema.TableName, Schema.TableType);
								hasFullTableSchema = true;
							}

							getPrimaryKeyTables();

							if (pkTables.ContainsKey(relationTableName))
							{
								pkName = getPrimaryKeyName();
								fkName = pkTables[relationTableName];
							}
							else
							{
								getForeignKeyTables();

								if (fkTables.ContainsKey(relationTableName))
								{
									pkName = fkTables[relationTableName].ColumnName;
									fkName = fkTables[relationTableName].PrimaryColumnName;
								}
							}

							((RelationalObject)mapping.Value).LoadAsRelation(relationTable, fkName, GetDataValue<int>(pkName), dataRelationMapping.Aggregates);
						}
						else if (mapping is DataMapping)
						{
							DataMapping dataMapping = (DataMapping)mapping;

							if (!String.IsNullOrEmpty(dataMapping.ColumnName))
							{
								object newObj = null;

								try
								{
									newObj = GetDataValue<object>(dataMapping.ColumnName);
								}
								catch
								{
									throw new Exception(String.Format("No data exists in column \"{0}\" for Relation \"{1}\" in BusinessObject \"{2}\"", dataMapping.ColumnName, Name, GetType().FullName));
								}

								if (newObj != null)
								{
									if (mapping.Type.IsEnum)
									{
										mapping.Value = Enum.Parse(dataMapping.Type, newObj.ToString());
									}
									else
									{
										mapping.Value = newObj;
									}
								}
								else if (mapping.Type != typeof(object))
								{
									// Get a default value

									switch (mapping.Type.Name)
									{
										case "Boolean": mapping.Value = default(Boolean); break;
										default: break;
									}
								}
							}
						}
						else
						{
							throw new Exception(String.Format("Mapping \"{0}\" cannot be an abstract Mapping", Name));
						}

						mapping.IsLoaded = true;
					}

					return mapping.Value;
				}
				
				throw new Exception(String.Format("Mapping \"{0}\" does not exist in MappingList", Name));
			}

			return null;
		}

		public T GetObjectValue<T>(string Name)
		{
			object obj = GetObjectValue(Name);

			if (obj != null)
			{
				Tools tools = new Tools();
				return tools.ConvertType<T>(obj);
			}

			return default(T);
		}

		public void SetValue(string Name, object Obj)
		{
			if (Schema != null)
			{
				MappingList mappings = getMappings();

				if (mappings.ContainsKey(Name))
				{
					mappings[Name].Value = Obj;
				}
				else
				{
					throw new Exception(String.Format("Mapping \"{0}\" does not exist in MappingList", Name));
				}
			}
		}

		///// <summary>
		///// Saves this object's state to the underlying data source.
		///// </summary>
		//public void Save()
		//{
		//    Save(String.Empty);
		//}

		///// <summary>
		///// Saves this object's state to the underlying data source.
		///// </summary>
		///// <param name="userID">The user ID.</param>
		//public void Save(int userID)
		//{
		//    Save(userID.ToString());
		//}

		///// <summary>
		///// Saves this object's state to the underlying data source.
		///// </summary>
		///// <param name="userID">The user ID.</param>
		//public void Save(Guid userID)
		//{
		//    string sUserID = userID.ToString();
		//    Save(sUserID);
		//}

		//public void Save()
		//{
		//    if (Schema != null)
		//    {
		//        foreach (Mapping mapping in getMappings())
		//        {
		//            if (mapping is DataMapping)
		//            {
		//                DataMapping dm = (DataMapping)mapping;

		//                SetDataValue(dm.ColumnName, dm.Value);
		//            }
		//            else if (mapping is DataRelationMapping)
		//            {
		//                DataRelationMapping drm = (DataRelationMapping)mapping;

		//                if (drm.Value is RelationalObject)
		//                {
		//                    RelationalObject ro = (RelationalObject)drm.Value;
		//                    this.Set
		//                }


		//            }



					
		//        }
		//    }
		//}

		public DT GetDataValue<DT>(string ColumnName)
		{
			DT oOut = default(DT);

			if (columnSettings != null)
			{
				oOut = columnSettings.GetValue<DT>(ColumnName);
			}

			return oOut;
		}

		public object GetDataValue(string ColumnName)
		{
			object oOut = null;

			if (columnSettings != null)
			{
				oOut = columnSettings.GetValue(ColumnName);
			}

			return oOut;
		}

		public void SetDataValue(string ColumnName, object Obj)
		{
			if (columnSettings != null)
			{
				columnSettings.SetValue(ColumnName, Obj);
			}
		}

		public bool CheckDataExists(string DataColumnName)
		{
			string dataColumnName = DataColumnName.ToLower();

			if (columnSettings != null
				&& columnSettings.Contains(dataColumnName)
				&& columnSettings[dataColumnName] != null
				&& columnSettings[dataColumnName].CurrentValue != DBNull.Value)
			{
				return true;
			}

			return false;
		}

		#endregion

		//public void Save()
		//{
		//    foreach (KeyValuePair<string, Mapping> mapping in getMappings())
		//    {
		//        DataMapping dataMapping = mapping as DataMapping;

		//        if (dataMapping != null)
		//        {
		//            SetDataValue(dataMapping.ColumnName, dataMapping.Value);
		//        }

		//    }

		//}

		#region *** Load Data ***

		//public DataObject<ItemType> Load<T>(T SubSonicRecord)
		//    where T : ActiveRecord<T>, new()
		//{
		//    Schema = GetTableSchema();
		//    columnSettings = SubSonicRecord.GetColumnSettings();

		//    return this;
		//}

		public DataObject<ItemType> Load(IDataReader Reader)
		{
			Schema = GetTableSchema();
			columnSettings = new TableSchema.TableColumnSettingCollection();

			try
			{
				if (Reader.Read())
				{
					foreach (TableSchema.TableColumn column in Schema.Columns)
					{
						columnSettings.Add(new TableSchema.TableColumnSetting(column.ColumnName.ToLower(), Reader[column.ColumnName]));
					}
				}
			}
			finally
			{
				if (Reader != null)
				{
					Reader.Close();
				}
			}

			return this;
		}

		public DataObject<ItemType> Load(Dictionary<string, object> Fields)
		{
			Schema = GetTableSchema();
			columnSettings = new TableSchema.TableColumnSettingCollection();

			foreach (KeyValuePair<string, object> field in Fields)
			{
				columnSettings.Add(new TableSchema.TableColumnSetting(field.Key.ToLower(), field.Value));
			}

			return this;
		}

		public DataObject<ItemType> Load(DataRow Row)
		{
			Schema = GetTableSchema();
			columnSettings = new TableSchema.TableColumnSettingCollection();

			foreach (DataColumn column in Row.Table.Columns)
			{
				columnSettings.Add(new TableSchema.TableColumnSetting(column.ColumnName.ToLower(), Row[column]));
			}

			return this;
		}

		public DataObject<ItemType> Load(ItemType Item)
		{
			Schema = GetTableSchema();
			columnSettings = Item.columnSettings;

			return this;
		}

		//internal BusinessObject<ItemType> LoadFromCache<ListType>(int Id)
		//{
		//    LoadSchema();
		//    columnSettings = ColumnSettings;

		//    return this;
		//}

		internal DataObject<ItemType> Load(TableSchema.TableColumnSettingCollection ColumnSettings)
		{
			Schema = GetTableSchema();
			columnSettings = ColumnSettings;

			return this;
		}

		/// <summary>
		/// Returns BusinessObject as a Relation from a cached BusinessObjectList
		/// </summary>
		/// <typeparam name="ItemType">Item of type BusinessObject</typeparam>
		/// <typeparam name="ListType">List of type BusinessObjectList</typeparam>
		/// <param name="ForeignKeyName"></param>
		/// <param name="PrimaryKeyValue"></param>
		/// <returns></returns>
		public DataObject<ItemType> LoadAsRelationFromCache<ListType>(string ForeignKeyName, object PrimaryKeyValue)
			where ListType : DataObjectList<ItemType, ListType>, new()
		{
			ListType businessObjects = new ListType();
			businessObjects.LoadAll();
			
			foreach (ItemType item in businessObjects)
			{
				if (item.GetDataValue(ForeignKeyName).Equals(PrimaryKeyValue))
				{
					loadInternal(item.columnSettings);

					return this;
				}
			}

			return this;
		}

		#endregion

		#region *** Relational Objects ***

		public void LoadRelations()
		{
			LoadRelations(getMappings().Names);
		}
		
		#endregion

		#region IEnumerable<ItemType> Members

		public IEnumerator<ItemType> GetEnumerator()
		{
			throw new NotImplementedException();
		}

		#endregion

		
	}
}
